#include "Mesh.h"
#include <glad/glad.h>
#include <iostream>
#include <glm/gtc/matrix_transform.hpp>
#include <utility>

using namespace std;
using namespace glm;

Mesh::Mesh(const vector<Vertex> &vertices, const vector<uint32_t> &indices, const Texture &texture)
{
    this -> vertices = vertices;
    this -> indices = indices;
    this -> texture = texture;

    VAO = 0;
    VBO = 0;
    EBO = 0;

    SetupMesh();
}

void Mesh::SetupMesh()
{
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, int64_t(vertices.size() * sizeof(Vertex)), vertices.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, int64_t(indices.size() * sizeof(uint32_t)), indices.data(), GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Position));
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
    for (int i = 0; i < MAX_BONE_LENGTH; ++i)
    {
        glVertexAttribIPointer(3 + i, 1, GL_UNSIGNED_INT, sizeof(Vertex), (void*)(offsetof(Vertex, BoneId) + sizeof(Vertex::BoneId[0]) * i));
        glVertexAttribPointer(3 + MAX_BONE_LENGTH + i, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(offsetof(Vertex, BoneWidget) + sizeof(Vertex::BoneWidget[0]) * i));
    }

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    for (int i = 0; i < MAX_BONE_LENGTH; ++i)
    {
        glEnableVertexAttribArray(3 + i);
        glEnableVertexAttribArray(3 + MAX_BONE_LENGTH + i);
    }

    glBindVertexArray(0);
}

void Mesh::Draw(const gl::IShader &shader) const
{
    glActiveTexture(GL_TEXTURE0);
    shader.setInt("texture0", 0);
    glBindTexture(GL_TEXTURE_2D, texture.TexId);

    shader.setMat4("modelMatrix", modelMatrix);

    if (animVec != nullptr) {
        auto boneLength = int(animVec->size() > MAX_BONE_ANIM_LENGTH ? MAX_BONE_ANIM_LENGTH : animVec->size());
        shader.setMat4Array("boneAnimMatrix", boneLength, (*animVec).data());
    }

    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, int32_t(indices.size()), GL_UNSIGNED_INT, nullptr);
    glBindVertexArray(0);
    glActiveTexture(GL_TEXTURE0);
}

void Mesh::SetModelMatrix(const mat4 &mMat)
{
    modelMatrix = mMat;
}

void Mesh::SetAnimVec(std::shared_ptr<std::vector<glm::mat4>> anim)
{
    animVec = std::move(anim);
}
